/*
 * Editeur de commande
 * @author Florent SIEBERT
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include "editline.h"
#include "history.h"

#define MAX_PROMPT_SIZE 60

#define CtrlA 0x1
#define CtrlB 0x2
#define CtrlD 0x4
#define CtrlE 0x5
#define CtrlF 0x6
#define CtrlH 0x8
#define CtrlK 0xB
#define CtrlL 0xC
#define CtrlM 0xA
#define CtrlN 0xE
#define CtrlP 0x10
#define CtrlY 0x19
#define Del 0x7F

#define FlecheChar1 0x1B
#define FlecheChar2 0x5B
#define Haut 'A'
#define Bas 'B'
#define Droite 'C'
#define Gauche 'D'

char *line;
int pos;
char promptComplet[MAX_PROMPT_SIZE];

/**
 * Changement de mode du terminal 
 */
void mode_raw(int activer) {
	static struct termios cooked;
	static int raw_actif = 0;
	if (raw_actif == activer) {
		return;
	}
	if(activer) {
		struct termios raw;
		tcgetattr(STDIN_FILENO, &cooked); // On mémorise l'état du terminal
		tcgetattr(STDIN_FILENO, &raw); 
		raw.c_lflag &= ~(ICANON | ECHO); // Non canonique, sans echo
		raw.c_cc[VMIN] = 1; // Débloque à 1 caractère
		raw.c_cc[VTIME] = 0; // Sans limite de temps
		tcsetattr(STDIN_FILENO, TCSANOW, &raw); // On applique
	} else {
		tcsetattr(STDIN_FILENO, TCSANOW, &cooked); // On rétablit
	}
	raw_actif = activer;
}

/**
 * On recule le curseur d'une case
 */
void reculer() {
	if(pos>0) {
		putchar('\b');
		--pos;
	}
}

/**
 * On avance le curseur d'une case
 */
void avancer() {
	if(pos<strlen(line))
		putchar(line[pos++]);
}

/**
 * On ramène le curseur au début
 */
void retourDebut() {
	putchar('\r');
	pos=0;
	printf("%s", promptComplet);
}

/**
 * On efface la ligne
 */
void effacerLigne() {
	retourDebut();
	int l = strlen(line);
	while(pos<l) {
		putchar(' ');
		line[pos++] = '\0';
	}
	retourDebut();
}

/**
 * On retourne à la position tmpPos
 */
void retourPosition(int tmpPos) {
	while(pos>tmpPos) {
		reculer();
	}
}

/**
 * On supprime le caractère courant
 */
void supprimerCaractere() {
	int tmpPos = pos;
	while(pos<strlen(line)) {
		line[pos] = line[++pos];
		putchar(line[pos]);
	}
	putchar(' ');
	line[pos]='\0';
	retourPosition(tmpPos);
}

/**
 * On remplace la ligne actuelle par h
 */
void remplacer_cmd(char *h) {
	effacerLigne();
	int i;
	for(i=0 ; i<strlen(h) ; i++) {
		line[i] = h[i];
		avancer();
	}
}

/**
 * On affiche la commande précédente de l'historique
 */
void previous_cmd() {
	remplacer_cmd(previous_history());
}

/**
 * On affiche la commande suivante de l'historique
 */
void next_cmd() {
	remplacer_cmd(next_history());
}

/**
 * Cette fontion prend en paramètre un prompt et un buffer s de taille size
 * où la ligne devra être rangée. Elle rend l'adresse de la ligne
 * saisie ou NULL si la fin de fichier est atteinte.
 */
char *editline(char *s, const int size, const char *prompt) {

	int tmpPos = 0;
	char c;
	int i;

	line = malloc(size * sizeof(char));
	line[0] = '\0';
	pos = 0;

	char *backup = malloc(size * sizeof(char));
	int backupLength = 0;
	

	sprintf(promptComplet, "%s", prompt);

	mode_raw(1); // On passe en mode non canonique
	printf("%s", promptComplet);

	while((c=getchar())) {
		switch(c) {
			case FlecheChar1: // Gestion des quatre flèches directionnelles
				if((c=getchar())==FlecheChar2) {
					switch(c=getchar()) {
						case Haut:
							previous_cmd();
							break;
						case Bas:
							next_cmd();
							break;
						case Gauche:
							reculer();
							break;
						case Droite:
							avancer();
							break;
					}
				}
				break;
			case CtrlA:
				retourDebut();
				break;
			case CtrlB:
				reculer();
				break;
			case CtrlD:
				// Si la ligne est vide on sort 
				if(strlen(line)==0) {
					mode_raw(0); // Sans oublier de rétablir le mode canonique
					free(line);
					free(backup);
					return NULL;
				}
				// Sinon on supprime le caractère courant
				if(pos < strlen(line)) {
					supprimerCaractere();
				}
				break;
			case CtrlE:
				while(pos<strlen(line))
					avancer();
				break;
			case CtrlF:
				avancer();
				break;
			case Del:
			case CtrlH: // On supprime le caractère précédent
				if(pos>0) {
					reculer();
					supprimerCaractere();
				}
				break;
			case CtrlK: // On coupe jusqu'à la fin de ligne
				tmpPos = pos;
				backupLength = 0;
				while(pos<strlen(line)) {
					backup[pos-tmpPos]=line[pos];
					putchar(' ');
					++pos;
					++backupLength;
				}
				line[tmpPos]='\0';
				backup[backupLength]='\0';
				retourPosition(tmpPos);
				break;
			case CtrlL:
				tmpPos = pos;
				retourDebut();
				while(pos<strlen(line)) {
					avancer();
				}
				retourPosition(tmpPos);
				break;
			case CtrlM: // On exécute
				while(pos<strlen(line))
					avancer();
				putchar('\n');
				strcpy(s, line);
				mode_raw(0);
				free(line);
				free(backup);
				return s;
			case CtrlN:
				next_cmd();
				break;
			case CtrlP:
				previous_cmd();
				break;
			case CtrlY: // On colle ce qui a été coupé par CtrlK
				if(strlen(line)+strlen(backup)<size) {
					tmpPos = pos + backupLength;
					for(i=0 ; i<strlen(backup) ; i++) {
						backup[i+backupLength] = line[pos];
						line[pos] = backup[i];
						putchar(backup[i]);
						++pos;
					}
					retourPosition(tmpPos);
				}
				break;
			default:
				if(c <= 0x1F) continue;
				if(strlen(line)<size) {
					tmpPos = pos;
    				int i;
					line[strlen(line)+1] = '\0';
    				for (i = strlen(line) ; i>pos ; i--) {
	        			line[i] = line[i-1];
    				}
    				line[pos] = c;
    				while (pos<strlen(line))
						avancer();
    				retourPosition(tmpPos+1);
				} else {
					line[size] = '\0';
				}
		}
		fflush(stdout);
	}
	mode_raw(0);
	free(line);
	free(backup);
	return s;
}
